/* Gluten Library -- Window
 *
 * A window, represents the entry point for most of the GLUT library.
 *
 * $AUTHOR$    res
 * $UPDATE$    r19
 */

/* LICENSES ********************************************************************

   Copyright (c) 2013- Reuben E. Smith

   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   copies of the Software, and to permit persons to whom the Software is
   furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   THE SOFTWARE.

 *******************************************************************************/


// TODO Add support for subwindows.


#ifndef GLUTEN_WINDOW_HPP
#define GLUTEN_WINDOW_HPP

#include <string>
#include <GL/glew.h>
#include <GL/freeglut.h>


// res: freeglut doesn't seem to define most key codes...? Adding them as I
//      need them.
enum GLUT_KEY
{
   GLUT_KEY_ENTER    = 0x0D,
   GLUT_KEY_ESC      = 0x1B,
   GLUT_KEY_SPACE    = 0x20,
   GLUT_KEY_TAB      = 0x09,

   GLUT_KEY_0 = '0',
   GLUT_KEY_1 = '1',
   GLUT_KEY_2 = '2',
   GLUT_KEY_3 = '3',
   GLUT_KEY_4 = '4',
   GLUT_KEY_5 = '5',
   GLUT_KEY_6 = '6',
   GLUT_KEY_7 = '7',
   GLUT_KEY_8 = '8',
   GLUT_KEY_9 = '9',

   GLUT_KEY_A = 'a',
   GLUT_KEY_B = 'b',
   GLUT_KEY_C = 'c',
   GLUT_KEY_D = 'd',
   GLUT_KEY_E = 'e',
   GLUT_KEY_F = 'f',
   GLUT_KEY_G = 'g',
   GLUT_KEY_H = 'h',
   GLUT_KEY_I = 'i',
   GLUT_KEY_J = 'j',
   GLUT_KEY_K = 'k',
   GLUT_KEY_L = 'l',
   GLUT_KEY_M = 'm',
   GLUT_KEY_N = 'n',
   GLUT_KEY_O = 'o',
   GLUT_KEY_P = 'p',
   GLUT_KEY_Q = 'q',
   GLUT_KEY_R = 'r',
   GLUT_KEY_S = 's',
   GLUT_KEY_T = 't',
   GLUT_KEY_U = 'u',
   GLUT_KEY_V = 'v',
   GLUT_KEY_W = 'w',
   GLUT_KEY_X = 'x',
   GLUT_KEY_Y = 'y',
   GLUT_KEY_Z = 'z',
};



namespace Gluten
{
   //! \class Window
   //! \brief A freeglut window wrapper. glutInit must be called first and
   //! \brief glutMainLoop some time after it has been created.
   class Window
   {
   public:
      static const int DEFAULT_DISPLAY_MODE = GLUT_DOUBLE | GLUT_RGBA;
      static const int DEFAULT_X = -1;
      static const int DEFAULT_Y = -1;
      static const unsigned int DEFAULT_WIDTH   = 640;
      static const unsigned int DEFAULT_HEIGHT  = 480;


      //! \brief Default constructor.
      Window();

      //! \brief Destructor!
      virtual ~Window();

      //! \brief Create parameterized window.
      //! \param[in]    title          window title text
      //! \param[in]    mode           window display mode
      //! \param[in]    x              x coordinate, from screen left in pixels;
      //!                              negatives let the window manager decide
      //! \param[in]    y              y coordinate, from screen top in pixels;
      //!                              negatives let the window manager decide
      //! \param[in]    w              width in pixels
      //! \param[in]    h              height in pixels
      void create(
         const std::string& title, 
         unsigned int mode = DEFAULT_DISPLAY_MODE,
         int x = DEFAULT_X, int y = DEFAULT_Y, 
         unsigned int w = DEFAULT_WIDTH, unsigned int h = DEFAULT_HEIGHT);

      //! \brief Destroy the window.
      void destroy();

      //! \brief Get the currently set cursor.
      //! \return       window cursor, as enumerated by freeglut
      int getCursor() const;

      //! \brief Set the window cursor.
      //! \param[in]    cursor         cursor icon to use, as enumerated by
      //!                              freeglut
      void setCursor(int cursor);

      //! \brief Set key repeat state for this window.
      //! \param[in]    state          whether key repeat should be on
      void setKeyRepeat(bool state);

      //! \brief Get window position.
      //! \param[out]   x              window x-coordinate, in pixels
      //! \param[out]   y              window y-coordinate, in pixels
      void getPosition(int& x, int& y) const;

      //! \brief Set window position.
      //! \param[in]    x              window x-coordinate, in pixels
      //! \param[in]    y              window y-coordinate, in pixels
      void setPosition(int x, int y);

      //! \brief Get window size.
      //! \param[out]   w              window width, in pixels
      //! \param[out]   h              window height, in pixels
      void getSize(unsigned int& w, unsigned int& h) const;

      //! \brief Set window size.
      //! \param[in]    w              window width, in pixels
      //! \param[in]    h              window height, in pixels
      void setSize(unsigned int w, unsigned int h);

      //! \brief Get window title.
      std::string getTitle() const;

      //! \brief Set window title.
      void  setTitle(const std::string& title);

      //! \brief Set window timer.
      //! \param[in]    delay          delay in milliseconds for timer to fire
      //! \param[in]    value          value to send to timer handler
      void setTimer(unsigned int delay, int value);

      //! \brief Test if OpenGL extension is supported.
      //! \param[in]    name           name of extension to check
      //! \return       true if supported
      bool isExtensionSupported(const std::string& name) const;


      //-- EVENTS -----------------------------------------------------------//
      //! \brief Called immediately after window is created.
      virtual void onCreate() {}

      //! \brief Called before GLUT window is destroyed.
      virtual void onDestroy() {}

      //! \brief Called when window needs to be redrawn.
      virtual void onDisplay() = 0;

      //! \brief Called when window is resized.
      //! \param[in]  w              new window width
      //! \param[in]  h              new window height
      virtual void onResize(int /*w*/, int /*h*/) {}

      //! \brief Called when key is pressed. This includes special keys.
      //! \param[in]  key            key code of key pressed
      //! \param[in]  mx             mouse x-coord at time of key press
      //! \param[in]  my             mouse y-coord at time of key press
      virtual void onKeyDown(int /*key*/, int /*mx*/, int /*my*/) {}

      //! \brief Called when key is released. This includes special keys.
      //! \param[in]  key            key code of key pressed
      //! \param[in]  mx             mouse x-coord at time of key press
      //! \param[in]  my             mouse y-coord at time of key press
      virtual void onKeyUp(int /*key*/, int /*mx*/, int /*my*/) {}

      //! \brief Called when mouse enters window viewing area.
      virtual void onMouseEnter() {}

      //! \brief Called when mouse leaves window viewing area.
      virtual void onMouseExit() {}

      //! \brief Called when mouse is moved.
      //! \param[in]  mx             mouse x-coord moved to
      //! \param[in]  my             mouse y-coord moved to
      virtual void onMouseMove(int /*mx*/, int /*my*/) {}

      //! \brief Called when mouse is moved while pressing mouse button.
      //! \param[in]  mx             mouse x-coord moved to
      //! \param[in]  my             mouse y-coord moved to
      virtual void onMouseDrag(int /*mx*/, int /*my*/) {}

      //! \brief Called when mouse button is pressed.
      //! \param[in]  button         mouse button pressed
      //! \param[in]  mx             mouse x-coord when pressed
      //! \param[in]  my             mouse y-coord when pressed
      virtual void onMouseDown(int /*button*/, int /*mx*/, int /*my*/) {}

      //! \brief Called when mouse button is released.
      //! \param[in]  button         mouse button released
      //! \param[in]  mx             mouse x-coord when released
      //! \param[in]  my             mouse y-coord when released
      virtual void onMouseUp(int /*button*/, int /*mx*/, int /*my*/) {}

      //! \brief Called when mouse wheel is scrolled.
      //! \param[in]  wheel          index of wheel moved
      //! \param[in]  dir            direction wheel moved
      //! \param[in]  mx             mouse x-coord when moved
      //! \param[in]  my             mouse y-coord when moved
      virtual void onMouseWheel(int /*wheel*/, int /*dir*/, 
         int /*mx*/, int /*my*/) {}

      //! \brief Called when timer event fires.
      //! \param[in]  value          value sent on timer
      virtual void onTimer(int /*value*/) {}

      //! \brief Called when a popup menu is opened.
      //! \param[in]  mx             mouse x-coord when opened
      //! \param[in]  my             mouse y-coord when opened
      virtual void onMenuOpen(int /*mx*/, int /*my*/) {}

      //! \brief Called when a popup menu is closed.
      //! \param[in]  mx             mouse x-coord when closed
      //! \param[in]  my             mouse y-coord when closed
      virtual void onMenuClose(int /*mx*/, int /*my*/) {}
      //-- END EVENTS -------------------------------------------------------//


      //-- INTERNAL USE -----------------------------------------------------//
      static void windowClose();
      static void windowDisplay();
      static void windowPosition(int x, int y);
      static void windowReshape(int w, int h);
      static void windowKeyDown(unsigned char key, int mx, int my);
      static void windowKeyUp(unsigned char key, int mx, int my);
      static void windowSKeyDown(int key, int mx, int my);
      static void windowSKeyUp(int key, int mx, int my);
      static void windowMouseEntry(int state);
      static void windowMouseMove(int mx, int my);
      static void windowMouseDrag(int mx, int my);
      static void windowMousePress(int button, int state, int mx, int my);
      static void windowMouseWheel(int wheel, int dir, int mx, int my);
      static void windowTimer(int value);
      static void windowMenu(int state, int mx, int my);
      //-- END INTERNAL USE -------------------------------------------------//


   private:
      //! \brief Copy constructor. Inaccessible.
      Window(const Window&) {}

      //! \brief Assignment operator. Inaccessible.
      Window& operator=(const Window&) { return *this; }


   protected:
      int            _windowID;
      std::string    _windowTitle;
      unsigned int   _timerPeriod;
   };


   //! \brief Map mouse button presses to wheel up and down events.
   //! \param[in]       wheel          wheel to report event as
   //! \param[in]       up             mouse button to associate with up
   //! \param[in]       down           mouse button to associate with down
   //!
   //! The freeglut library seems to have issues under the X Window System with
   //! detecting mouse wheel events, so this function is used to internally map
   //! the detected mouse button events as wheel events.
   void makeSoftWheel(int wheel, int up, int down);
}

#endif

